using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using GraphMyCode.Web.Dtos;

namespace GraphMyCode.Web
{
	public static class LayoutService
	{
		public const string KK = "Kamada-Kawai";
		public const string FR = "Fruchterman-Reingold";

		private const int dpi = 96;
		private static readonly char[] Separators = new[] { ',' };

		private const string RegexGraph = "^\\s*(strict\\s+)?(di)?graph\\s*{\\s*((?<token>(?=\\S)([^;\"]|(\"([^\"\\\\\\n]|\\\\.)*\"))*(?<!\\s))\\s*((;\\s*)|(?=})))+}\\s*$";
		private const string RegexNode = "^(?<x>\")?(?(x)(([^\"\\\\\\n]|\\\\.)+(?<node-x>\"))|(?<node>[\\w]+))(\\s*\\[\\s*((?<name>\\w+)(?<hasv>\\s*=\\s*)?(?(hasv)((?<-hasv>)(?<x>\")?(?(x)(([^\"\\\\\\n]|\\\\.)*(?<value-x>\"))|(?<value>\\w+)))|(?<value>))((\\s*,\\s*)|(\\s+)|(?=\\])))+(?<!,\\s*)\\])?$";
		private const RegexOptions regexOptions = RegexOptions.ExplicitCapture | RegexOptions.CultureInvariant | RegexOptions.Singleline | RegexOptions.IgnoreCase;

		public static void RunKamadaKawai(GraphDto graph, double widht, double height)
		{
			Run(KK, graph, widht, height);
		}

		private static string RunProcess(string algorithm, string dotInput)
		{
			if (algorithm != KK && algorithm != FR)
				throw new ApplicationException("Unknown layout algorithm.");

			//System.Diagnostics.ProcessStartInfo psi = new System.Diagnostics.ProcessStartInfo();
			//psi.FileName = Server.MapPath("~/") + "TestApplication.exe";
			//psi.UseShellExecute = false;
			//psi.RedirectStandardOutput = true;
			//System.Diagnostics.Process p = new System.Diagnostics.Process();
			//p.StartInfo = psi;
			//p.Start();
			//Label1.Text = p.StandardOutput.ReadToEnd();
			//p.WaitForExit();

			string dir = @"C:\Projects\GraphMyCode\GraphMyCode.Web\bin\Layout\";

			ProcessStartInfo start = new ProcessStartInfo(dir + (algorithm == KK ? "neato" : "fdp"), @"-y -Tdot")
										{
											UseShellExecute = false,
											WorkingDirectory = dir,
											CreateNoWindow = true,
											RedirectStandardInput = true,
											RedirectStandardOutput = true
										};
			//start.RedirectStandardError = true;

			Process process = Process.Start(start);
			using (process.StandardInput)
			{
				process.StandardInput.Write(dotInput);
			}
			string dotOutput;
			using (process.StandardOutput)
			{
				dotOutput = process.StandardOutput.ReadToEnd();
			}
			process.WaitForExit();
			return dotOutput;
		}

		public static void Run(string algorithm, GraphDto graph, double width, double height)
		{
			if (algorithm == "random")
			{
				Random r = new Random();
				foreach (var vertex in graph.Vertices)
				{
					vertex.X = r.NextDouble();
					vertex.Y = r.NextDouble();
				}
			}
			else
			{
				using (StringWriter dotIn = new StringWriter())
				{
					Dictionary<string, VertexDto> idNodeMap = WriteGraph(dotIn, graph, width, height);
					string result = RunProcess(algorithm, dotIn.GetStringBuilder().ToString());
					Read(result, idNodeMap, width, height);
				}
			}

		}

		private static Dictionary<string, VertexDto> WriteGraph(TextWriter writer, GraphDto graph, double width, double height)
		{
			writer.WriteLine("graph {");
			writer.WriteLine(
				@"graph [
//		bgcolor = ""#FFFF00"",
		start = ""random"",
		center = ""true"",
		mode=""major"",
		model=""shortpath"",
		pack=""true"",
		packmode=""node"",
		overlap = ""true"",
		splines=""false""
	];
	node [label="""", fixedsize=""true"", style=""solid""];
	edge [style=""solid""]");
			writer.WriteLine(string.Format("graph [size=\"{0},{1}\", ratio=\"fill\"];",
										   ConvertToInches(width), ConvertToInches(height)));

			Dictionary<VertexDto, string> nodeIdMap = new Dictionary<VertexDto, string>();
			int i = 1;

			foreach (var vertex in graph.Vertices)
			{

				string id = i.ToString();

				writer.WriteLine(string.Format("\"{0}\" [width=\"{1}\", height=\"{2}\", shape=\"{3}\"];",
											   id,
											   ConvertToInches(vertex.Weight),
											   ConvertToInches(vertex.Weight),
											   vertex.Shape == NodeShape.Box ? "box" : "ellipse"));

				nodeIdMap.Add(vertex, id);
				++i;
			}

			foreach (var edge in graph.Edges)
			{
				writer.WriteLine(string.Format("\"{0}\" -- \"{1}\";", //" [weight=\"{2}\"];",
											   nodeIdMap[edge.Source], nodeIdMap[edge.Target]
					//(edge.Thick/2).ToString("g3",CultureInfo.InvariantCulture)
					//(edge.Thick).ToString("g1", CultureInfo.InvariantCulture)
									));
			}

			writer.WriteLine("}");

			Dictionary<string, VertexDto> idNodeMap = new Dictionary<string, VertexDto>(nodeIdMap.Count);
			foreach (VertexDto nodePresenter in nodeIdMap.Keys)
			{
				idNodeMap.Add(nodeIdMap[nodePresenter], nodePresenter);
			}
			return idNodeMap;
		}

		private static void Read(string dot, Dictionary<string, VertexDto> idNodeMap, double width, double height)
		{
			Regex graphRegex = new Regex(RegexGraph, regexOptions);
			Regex nodeRegex = new Regex(RegexNode, regexOptions);

			dot = dot.Replace("\r\n", "\n");

			Match graph = graphRegex.Match(dot);
			if (!graph.Success)
				return;

			double x0 = 0.0;
			double y0 = 0.0;
			double scaleX = 0.0;
			double scaleY = 0.0;
			bool foundBb = false;

			foreach (Capture token in graph.Groups["token"].Captures)
			{
				Match match = nodeRegex.Match(token.Value);
				if (!match.Success)
					continue;

				string node = ParseToken(match.Groups["node"].Value);

				if (!foundBb)
				{
					if (node.ToLower() == "graph")
					{
						for (int i = 0; i < match.Groups["name"].Captures.Count; ++i)
						{
							string attribName = match.Groups["name"].Captures[i].Value.ToLower();
							if (attribName == "bb")
							{
								string attribVal = ParseToken(match.Groups["value"].Captures[i].Value);
								string[] bb = attribVal.Split(Separators);
								double x1 = ConvertFromPoints(bb[0]);
								double y1 = ConvertFromPoints(bb[1]);
								double x2 = ConvertFromPoints(bb[2]);
								double y2 = ConvertFromPoints(bb[3]);
								double t;

								if (x2 < x1)
								{
									t = x2;
									x2 = x1;
									x1 = t;
								}
								if (y2 < y1)
								{
									t = y2;
									y2 = y1;
									y1 = t;
								}

								x2 -= x1;
								y2 -= y1;

								if (x2 != 0 && y2 != 0)
								{
									x0 = x1;
									y0 = y1;
									scaleX = width / x2;
									scaleY = height / y2;

									foundBb = true;
									break;
								}
							}
						}
					}
				}
				else
				{
					if (idNodeMap.ContainsKey(node))
					{
						for (int i = 0; i < match.Groups["name"].Captures.Count; ++i)
						{
							string attribName = match.Groups["name"].Captures[i].Value.ToLower();
							if (attribName == "pos")
							{
								string attribVal = ParseToken(match.Groups["value"].Captures[i].Value);
								string[] pos = attribVal.Split(Separators);
								VertexDto vertex = idNodeMap[node];
								vertex.X = ((ConvertFromPoints(pos[0]) - x0) * scaleX) / width;
								vertex.Y = ((ConvertFromPoints(pos[1]) - y0) * scaleY) / height;
								break;
							}
						}
					}
				}
			}
		}

		private static string ParseToken(string token)
		{
			return Regex.Replace(token, "\\\\.", x =>
													{
														if (x.Value[1] == '\n')
															return "";
														if (x.Value[1] == 'n')
															return "\n";
														return x.Value[1].ToString();
													}, regexOptions);
		}

		private static string ConvertToInches(double s)
		{
			return string.Format(CultureInfo.InvariantCulture, "{0:g3}", Math.Round(s / Convert.ToDouble(dpi), 3));
		}

		private static double ConvertFromPoints(string s)
		{
			return (double.Parse(s.Trim(), CultureInfo.InvariantCulture));
		}
	}
}